// This is the main DLL file.

#include "stdafx.h"
#include <atlstr.h>

#include "dx9fxutil.h"
#include <msclr/marshal.h>

using namespace System;
using namespace System::IO;
using namespace System::Runtime::InteropServices;
using namespace System::Text::RegularExpressions;
using namespace msclr::interop; 
using namespace dx9fxutil;

typedef HRESULT (WINAPI *ShaderCompilerType)
   (
        LPCSTR                          pSrcData,
        UINT                            SrcDataLen,
        CONST D3DXMACRO*                pDefines,
        LPD3DXINCLUDE                   pInclude,
        LPCSTR                          pFunctionName,
        LPCSTR                          pProfile,
        DWORD                           Flags,
        LPD3DXBUFFER*                   ppShader,
        LPD3DXBUFFER*                   ppErrorMsgs,
        LPD3DXCONSTANTTABLE*            ppConstantTable);

void PixelShaderCompiler::Compile(
    String^ shaderFileName,
    String^ shaderText,
    String^ psProfile,
    String^ destPSFile,
    LogWarningDelegate^ warningCallback,
    LogErrorDelegate^ errorCallback)
{
    marshal_context^ context = gcnew marshal_context();

    LPCSTR shaderTextLPCSTR = context->marshal_as<LPCSTR>(shaderText);
    LPCSTR psProfileLPCSTR = context->marshal_as<LPCSTR>(psProfile);

    LPD3DXBUFFER compiledShader;
    LPD3DXBUFFER errorMessages;

    int size = shaderText->Length;

    ShaderCompilerType shaderCompiler = ::D3DXCompileShader;

    HRESULT compileResult = 
        shaderCompiler(
            shaderTextLPCSTR, 
            size, 
            NULL, // pDefines
            NULL, // pIncludes
            "main", // entrypoint
            psProfileLPCSTR, // profile
            0, // compiler flags
            &compiledShader,
            &errorMessages,
            NULL   // constant table output
            );
    
    bool hasErrors = false;

    if (errorMessages != NULL)
    {
        char* nativeErrorString = (char*)(errorMessages->GetBufferPointer());
        String^ managedErrorString = context->marshal_as<String^>(nativeErrorString);

        String^ regexString = "(?<directory>[^@]+)memory\\((?<line>[^@]+),(?<col>[^@]+)\\): (?<descrip>[^@]+)";
        Regex^ errorRegex = gcnew Regex(regexString);

        for each (String^ errorOrWarning in managedErrorString->Split('\n'))
        {
            if (String::IsNullOrEmpty(errorOrWarning->Trim()))
                break;

            Match^ m = errorRegex->Match(errorOrWarning);

            String^ dir     = m->Groups["directory"]->Value;
            String^ line    = m->Groups["line"]->Value;
            String^ col     = m->Groups["col"]->Value;
            String^ descrip = m->Groups["descrip"]->Value;
            String^ file    = String::Concat(dir, shaderFileName);

            int     lineNum = 0;
            int     colNum = 0;
            bool    parsedLineNum = false;

            parsedLineNum = Int32::TryParse(line, lineNum);
            Int32::TryParse(col, colNum);

            if (!parsedLineNum)
            {
                // Just use the whole string as the description.
                descrip = errorOrWarning;
            }

            if (descrip->StartsWith("warning "))
            {
                warningCallback(0, file + "(" + lineNum + "," + colNum + ") : " + descrip, lineNum, colNum);
            }
            else
            {
                hasErrors = true;
                errorCallback(0, file + "(" + lineNum + "," + colNum + ") : " + descrip, lineNum, colNum);
            }
        }
    }

    if (!hasErrors)
    {
        char *nativeBytestream = (char *)(compiledShader->GetBufferPointer());
        array<unsigned char>^ arr = gcnew array<unsigned char>(compiledShader->GetBufferSize());

        for (DWORD i = 0; i < compiledShader->GetBufferSize(); i++)
        {
            arr[i] = nativeBytestream[i];
        }

        File::WriteAllBytes(destPSFile, arr);
    }
}